/**
 *
 */
package com.vivi.codereview.actors;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.vivi.codereview.WhatTheFuckException;
import com.vivi.codereview.tools.Log;
import com.vivi.codereview.tools.Utils;

/**
 * Is the associated reviewer of Manual Developper (DeveloperActor). This actor
 * will ask for the zip and the stream to the reviewer, then we will check
 * status of each impacted file, then try to unzip the fix, let the user doing
 * some diffs and, in the end, remove all hijacks (and destroy not in
 * sourceControl files).
 *
 * @author nvillard
 *
 */
public class ReviewerActor extends BaseActor {

	/**
	 * Regexp to retrieve all zipped files.
	 */
	protected static final Pattern LOG_FILE_LINE = Pattern.compile("(.+)\t.+");

	/**
	 * Which group do we consider in {@link ReviewerActor#LOG_FILE_LINE} to
	 * extract the filename.
	 */
	protected static final int LOG_FILE_LINE_GROUP = 1;

	/**
	 * Label to be displayed to user to stop the code review and undo hijacks.
	 */
	protected static final String ACTION_STOP = "Remove hijacks and exit";

	/**
	 * Label to be displayed to user to do diff for one file.
	 */
	protected static final String ACTION_DIFF_1_FILE = "diff between local file and ";

	protected static final String ACTION_DIFF_1_FILE_PATTERN = ACTION_DIFF_1_FILE + "{0}";

	/**
	 * Contructor.
	 */
	public ReviewerActor() {
		super();
	}

	@Override
	public int process() throws Exception {
		Log.logSeparator();
		// We look for jdevcc folder
		String jdevccFolder = getRootDirectory();
		String currentDir = new File(System.getProperty("user.dir")).getAbsolutePath();
		Log.log("current working directory is: " + currentDir, Log.D);
		File zippedFile = getZippedFile(currentDir, ZIP_FILENAME);
		// we get the folder (stream) we want to use
		String rootStream = selectFolder(jdevccFolder,
		        "Please select index of stream you want to use in list above (mandatory step):");
		String logFile = getZipUtil().readFileInZip(zippedFile, ZIP_LOG_FILENAME);
		Set<String> files = getFilesFromLog(rootStream, logFile);
		checkFilesStatus(rootStream, files);
		// We know all files are available now - no checkout, no hijack
		Map<String, String> mapPname = getPnames(rootStream, files);
		reviewActions(rootStream, zippedFile, mapPname);
		return Log.Exit.OK;
	}

	/**
	 * Check status for each file provided, to see if is correctly in ReadOnly
	 * status, otherwise will block until is in good status.
	 *
	 * @param stream
	 *            the stream
	 * @param files
	 *            the files we want to process
	 * @throws Exception
	 */
	protected void checkFilesStatus(String stream, Set<String> files) throws Exception {
		boolean areAllValids = true;
		do {
		  Log.log("We check status (checkout,  hijack...) for each file.", Log.I);
			areAllValids = true;
			for (String file : files) {
				areAllValids &= checkFileStatus(stream, file);
			}
		} while (!statusIsValid(areAllValids, "check status is not completed"));
		Log.log("check status is done", Log.D);
	}

	/**
	 * Check status for one file, to see if is correctly in readOnly state.
	 *
	 * @param stream
	 *            the stream
	 * @param file
	 *            the file
	 * @return true if is in good status (Read-only), false otherwise
	 * @throws Exception
	 */
	private boolean checkFileStatus(String stream, String file) throws Exception {
		File realFile = getSysUtil().getFile(stream, file);
		if (!getSysUtil().isReadOnly(realFile)) {
			try {
				if (getCcUtil().isCheckouted(stream, file)) {
					Log.log("This file is checkouted " + realFile, Log.W);
					return false;
				} else {
					Log.log("This file is hijacked " + realFile, Log.W);
					return false;
				}
			} catch (IOException e) {
				Log.log("We have an issue while checking clearcase status for " + realFile, Log.E);
				throw new WhatTheFuckException(Log.Exit.UNKOWN_CC_STATUS);
			}
		} else {
			Log.log("This impacted file is well ReadOnly " + realFile, Log.D);
			return true;
		}
	}

	/**
	 * Small method to allow display of a warning in case checkStatus is not ok,
	 * and to block until the user asks to check again.
	 *
	 * @param areAllFilesValid
	 *            checkStatus result
	 * @param msgIfNotValid
	 *            message to be displayed in case check is not ok
	 * @return returns areAllFilesValid
	 * @throws Exception
	 */
	protected boolean statusIsValid(boolean areAllFilesValid, String msgIfNotValid) throws Exception {
		if (areAllFilesValid) {
			return true;
		} else {
			Log.log(msgIfNotValid, Log.D);
			getInputUtil().getUserInput("Please correct situation and press any key to check again");
			return false;
		}
	}

	/**
	 * Retrieve the zip file to be considered.
	 *
	 * @param root
	 * @param zipFilename
	 * @return
	 */
	protected File getZippedFile(String root, String zipFilename) throws Exception {
		File file = getSysUtil().getFile(root, zipFilename);
		Log.logSeparator();
		String input = getInputUtil().getUserInput(
		        "Please provide the path to the zip file.\nDefault location will be " + file.getAbsolutePath());
		if (!getSelectRangeUtil().isNullSelectRange(input)) {
			file = new File(input);
		}
		Log.log("Selected zip file: " + file, Log.D);
		if (!file.exists()) {
			Log.log("You entered wrong zip location (doesn't exist). We cannot continue. Bye bye", Log.E);
			throw new WhatTheFuckException(Log.Exit.ZIP_FILE_NOT_FOUND);
		}
		return file;
	}

	/**
	 * Read the zip Log.log file and extract all files.
	 *
	 * @param stream
	 *            the stream folder we want to extract to
	 * @param log
	 *            the log file
	 * @return Set of files we extracted from log file, with absolute path
	 */
	protected Set<String> getFilesFromLog(String stream, String log) throws Exception {
		Set<String> files = new TreeSet<String>();
		List<String> lines = Arrays.asList(log.split("\n"));
		for (String line : lines) {
			Log.log("we try to read this line from Log.log file " + line, Log.D);
			Matcher matcher = LOG_FILE_LINE.matcher(line);
			if (matcher.find()) {
				String file = matcher.group(LOG_FILE_LINE_GROUP);
				String fullFile = getSysUtil().getFullFilename(stream, file);
				Log.log("We consider this file " + file + " with " + fullFile + " as fullname ", Log.D);
				files.add(file);
			} else {
				Log.log("This line is not handled " + line, Log.D);
			}
		}
		Log.log("We consider those files " + files.toString(), Log.D);
		return files;
	}

	/**
	 * Retrieve the pname for each file. pname is the way to knwo the full name
	 * in VOB (to allow diff).
	 *
	 * @param root
	 *            the stream folder
	 * @param files
	 * @return pname can be null if new file
	 */
	protected Map<String, String> getPnames(String root, Set<String> files) throws Exception {
		Map<String, String> mapPnames = new HashMap<String, String>();
		for (String file : files) {
			Log.log("Trying to get pname for" + file, Log.D);
			String pname = getCcUtil().getPname(root, file);
			Log.log("we get this pname for " + file + " : " + pname, Log.D);
			mapPnames.put(file, pname);
		}
		return mapPnames;
	}

	/**
	 * Gener Review action menu, hardcoding the stop action, and adding one diff
	 * entry for each relevant file.
	 *
	 * @param mapPname
	 *            Map between files and pname (null if new file, no diff
	 *            available)
	 * @return List of String to be displayed
	 * @throws Exception
	 */
	protected Set<String> getReviewActionMenu(Map<String, String> mapPname) throws Exception {
		Set<String> actions = new TreeSet<String>();
		actions.add(ACTION_STOP);
		for (Map.Entry<String, String> file : mapPname.entrySet()) {
			if (file.getValue() != null) {
				actions.add(Utils.formatString(ACTION_DIFF_1_FILE_PATTERN, file.getKey()));
			} else {
				Log.log("Since pname for " + file + "is null, we don't allow any diff", Log.D);
			}
		}
		return actions;
	}

	/**
	 * Call the diff process
	 * @param stream the stream
	 * @param file the selected file
	 * @param mapPname the Map between the file and the pname
	 * @throws Exception
	 */
	protected void launchDiffFile(String stream, String file, Map<String, String> mapPname) throws Exception {
		String pname = mapPname.get(file);
		if (pname != null) {
			Log.log("we have this file " + file + " and this pname " + pname, Log.D);
			getCcUtil().diffFile(stream, file, mapPname.get(file));
		} else {
			Log.log("This file " + file + " cannot be compared " + pname, Log.W);
		}
	}

	/**
	 * Display and manage menu for user to choose between many options
	 *
	 * @param root
	 *            the stream
	 * @param zippedFile
	 *            the zipped file
	 * @param mapPname
	 *            the Map with pnames for diff
	 * @throws Exception
	 */
	protected void reviewActions(String root, File zippedFile, Map<String, String> mapPname) throws Exception {
		getZipUtil().unzipFile(zippedFile.getAbsolutePath(), root);
		Set<String> actions = getReviewActionMenu(mapPname);
		boolean isLooping = true;
		Log.log("You can now use Eclipse + AgileReview to process CodeReview, or select one of the actions below.", Log.I);
		Log.log("list of actions " + actions.toString(), Log.D);
		while (isLooping) {
			Log.log("***** ACTIONS *****", Log.I);
			String action = getInputUtil().letUserChooseOne(actions, "Please enter index of item you want to do:", false);
			if (ACTION_STOP.equals(action)) {
				Log.log("we stop everything, as requested", Log.D);
				isLooping = false;
				// We need the pname as well, to check if the file has been
				// created or modified
				undoHijacks(root, mapPname);
			} else if (action != null && action.startsWith(ACTION_DIFF_1_FILE)) {
				String file = getFileFromActionMenu(action);
				launchDiffFile(root, file, mapPname);
			} else {
				Log.log("You entered something not valid, try again. " + action, Log.W);
			}
		}
	}

	/**
	 * Remove the hijack status for each file and update it. Remove the file if
	 * has been a created one.
	 * @param root the stream
	 * @param mapPname
	 */
	protected void undoHijacks(String root, Map<String, String> mapPname) throws Exception {
		for (Map.Entry<String, String> file : mapPname.entrySet()) {
			Log.log("is Undoing hijack for " + file.toString(), Log.D);
			File realFile = getSysUtil().getFile(root, file.getKey());
			if (file.getValue() == null) {
				Log.log("this file is removed because has no pname " + realFile, Log.D);
				boolean deleted = realFile.delete();
				if (!deleted) {
					Log.log("This file has not been deleted successfully: " + realFile, Log.W);
				} else {
					Log.log("This file has been successfully deleted: " + realFile, Log.D);
				}
			} else {
				Log.log("We update with overwrite to erase hijack status " + realFile, Log.D);
				try {
					getCcUtil().updateFile(root, file.getKey());
				} catch (IOException e) {
					Log.log("We were not able to undo hijack and update this file " + file.getKey() + " "
					        + e.getMessage(), Log.W);
				}
			}
		}

	}

	/**
	 * Regarding a value from the review action menu doing diff for a file, we extract the right file
	 * @param action
	 * @return
	 * @throws Exception
	 */
	protected String getFileFromActionMenu(String action) throws Exception {
		return action.substring(ACTION_DIFF_1_FILE.length());
	}

	@Override
	public String getName() {
		return "Manual Reviewer";
	}

}
